#include <slothttpd/data_buffer.hpp>
#include <slothttpd/logger.hpp>

#include <stdexcept>
#include <algorithm>

namespace slothttpd {

std::ostream &operator<<(std::ostream &os, const data_buffer &b)
{
    std::cerr <<"logging buffer: " << &b << std::endl;
    
    return os << "  buffer (" << &b << "): data=" << (void*)b.data << ", size: " << b.size
              << ", unprocessed(" << (void*)b.unprocessed_first << ", " << (void*)b.unprocessed_last << ")";
}

data_buffer::data_buffer(char *p, std::size_t bytes) :
    unprocessed_first(p), unprocessed_last(p),
    data(p), size(bytes)
{
}

data_buffer::data_buffer(void *p, std::size_t bytes) :
    data_buffer(reinterpret_cast<char*>(p), bytes)
{
}

data_buffer::data_buffer() noexcept :
    data_buffer(reinterpret_cast<void*>(0), 0)
{
}

char_range_t data_buffer::unprocessed() noexcept
{
    return char_range_t(unprocessed_first, unprocessed_last);
}

char_range_t data_buffer::processed() noexcept
{
    return char_range_t(data, unprocessed_first);
}

bool data_buffer::full() const noexcept
{
    return unprocessed_last == data + size;
}

char_range_t data_buffer::free_space() noexcept
{
    return char_range_t(unprocessed_last, data + size);
}

void data_buffer::remove_processed_data() noexcept
{
    std::size_t unproc_size = unprocessed().size();
    std::copy(unprocessed_first, unprocessed_last, data);
    unprocessed_first = data;
    unprocessed_last = unprocessed_first + unproc_size;
}

void data_buffer::reset() noexcept
{
    unprocessed_first = unprocessed_last = data;
}

void data_buffer::provide(std::size_t n)
{
    // TODO: remove check ?
    if (n > std::size_t(data + size - unprocessed_last)) {
        BOOST_THROW_EXCEPTION(std::logic_error("receive_buffer: try to provide too much data"));
    }
    
    unprocessed_last += n;
}

void data_buffer::consume(std::size_t n)
{
    // TODO: remove check ?
    if (n > std::size_t(unprocessed().size())) {
        BOOST_THROW_EXCEPTION(std::logic_error("receive_buffer: try to consume too much data"));
    }
    
    unprocessed_first += n;
}

data_buffer data_buffer::from_pool(generic_pool &pool, std::size_t size)
{
    return data_buffer(pool.allocate(size), size);
}

receive_buffer_list::receive_buffer_list(std::size_t s, std::size_t m, bool save_buffers) :
    save_buffers_(save_buffers),
    took_new_(false),
    buffer_size(s),
    max_buffers(m)
{
    if (m < 2)
        BOOST_THROW_EXCEPTION(std::logic_error("max buffers must be at least 1"));
}

receive_buffer_list::~receive_buffer_list()
{
    free();
}

char_range_t receive_buffer_list::get_free_space()
{
    took_new_ = false;
    
    if (empty_.empty()) {
        if (total_buffers() < max_buffers) {
            data_buffer *b = allocate(buffer_size);
            
            empty_.push_back(*b);
            
            took_new_ = true;
        }
    }
    
    if (!empty_.empty()) {
        return current().free_space();
    }
    
    return char_range_t(nullptr, nullptr);
}


char_range_t receive_buffer_list::get_data()
{
    if (!ready_.empty()) {
        return ready_.front().unprocessed();
    } else if (!empty_.empty()){
        return current().unprocessed();
    }
    
    return char_range_t(nullptr, nullptr);
}

void receive_buffer_list::provide(std::size_t n)
{
    current().provide(n);
    
    if (current().free_space().empty()) {
        ready_buffer(current());
    }
}

void receive_buffer_list::consume(std::size_t n)
{
    while (!ready_.empty() && n > 0) {
        data_buffer &f = ready_.front();
        
        std::size_t consume_bytes = std::min(n, f.size);
        
        f.consume(consume_bytes);
        n -= consume_bytes;
        
        if (f.unprocessed().empty()) {
            consume_buffer(f);
        }
    }
    
    if (n > 0 && !empty_.empty()) {
        current().consume(n);
    }
}

data_buffer &receive_buffer_list::current()
{
    return empty_.front();
}

data_buffer &receive_buffer_list::prev()
{
    return ready_.back();
}

void receive_buffer_list::reset()
{
    empty_buffer_list(ready_);
    empty_buffer_list(consumed_);
    
    if (!empty_.empty()) {
        current().remove_processed_data();
    }
}

void receive_buffer_list::switch_dont_save()
{
    save_buffers_ = false;
    
    while (!consumed_.empty()) {
        empty_buffer(consumed_.front());
    }
}

void receive_buffer_list::empty_buffer_list(receive_buffer_list::list_t &list)
{
    while (!list.empty()) {
        empty_buffer(list.front());
    }
}

std::size_t receive_buffer_list::total_buffers() const
{
    return ready_.size() + empty_.size() + consumed_.size();
}

bool receive_buffer_list::took_new() const
{
    return took_new_ && !ready_.empty() && !consumed_.empty();
}

void receive_buffer_list::free()
{
    free_list(ready_);
    free_list(empty_);
    free_list(consumed_);
}

void receive_buffer_list::disable()
{
    free();
    max_buffers = 0;
}

void receive_buffer_list::ready_buffer(data_buffer &b)
{
    b.unlink();
    ready_.push_back(b);
}

void receive_buffer_list::consume_buffer(data_buffer &b)
{
    if (save_buffers_) {
        b.unlink();
        consumed_.push_back(b);
    } else {
        empty_buffer(b);
    }
}

void receive_buffer_list::empty_buffer(data_buffer &b)
{
    b.unlink();
    b.reset();
    empty_.push_back(b);
}

data_buffer *receive_buffer_list::allocate(std::size_t size)
{
    void *p = std::malloc(size + sizeof(data_buffer));
    
    if (p != nullptr) {
        auto c = reinterpret_cast<data_buffer*>(p);
        new (c) data_buffer(static_cast<char*>(p) + sizeof(data_buffer), size);
        
        return c;
    } else {
        throw std::bad_alloc();
    }
}

void receive_buffer_list::free_list(receive_buffer_list::list_t &l)
{
    while (!l.empty()) {
        data_buffer &b = l.front();
        b.unlink();
        std::free(reinterpret_cast<void*>(&b));
    }
}

std::ostream &operator<<(std::ostream &os, const receive_buffer_list &list)
{
    os << "receiving_buffer_list (empty buffers):\n";
    for (auto &b : list.empty_) {
        os << b << std::endl;
    }
    
    return os;
}

} //namespace slothttpd
